package edu.hawaii.halealohacli.processor;

import javax.xml.datatype.XMLGregorianCalendar;
import org.wattdepot.client.BadXmlException;
import org.wattdepot.client.MiscClientException;
import org.wattdepot.client.NotAuthorizedException;
import org.wattdepot.client.ResourceNotFoundException;
import org.wattdepot.client.WattDepotClient;
import org.wattdepot.resource.source.jaxb.Source;
import org.wattdepot.util.tstamp.Tstamp;

/**
 * Processor class to parse and store arguments.
 * 
 * @author Russell Vea for ICS 314 Fall 2011
 * @author Jayson T. Gamiao
 * @author Branden Ogata
 */
public class Processor {

  private WattDepotClient client;
  private Source source;
  private String sourceName;
  private XMLGregorianCalendar timestampStart;
  private XMLGregorianCalendar timestampEnd;
  private int interval = 10;
  private int goal = 10;

  /**
   * Constructor with given WattDepotClient.
   * 
   * @param client the WattDepotClient to query.
   */
  public Processor(WattDepotClient client) {
    this.client = client;
  }

  /**
   * process string for args.
   * 
   * @param args 1 to 3 arguments to parse.
   * @throws Exception upon error with isSource().
   */
  public void process(String args) throws Exception {
    String[] strings = args.split(" ");
    if (strings.length <= 3) {
      int currentArgument = 0;

      for (String s : strings) {
        try {
          // The if-else logic below ensures that Strings are long enough
          // for the indices that will be searched
          if (s.length() >= 4) {
            if (Integer.valueOf(s.substring(0, 4)) <= 2011) {
              setTimestamp(s);
            }
          }
          else if (s.length() >= 1) {
            // monitor-power
            if (strings.length == 2) {
              this.interval = Integer.parseInt(s);
            }
            // monitor-goal
            // If it is the first argument, then the argument is the goal
            else if ((strings.length == 3)
                && ((currentArgument == 1) && (Integer.valueOf(s.substring(0, 1)) >= 1))) {
              this.goal = Integer.parseInt(s);
            }
            // If it is the second argument, then the argument is the interval
            else if ((strings.length == 3)
                && ((currentArgument == 2) && (Integer.valueOf(s.substring(0, 1)) >= 1))) {
              this.interval = Integer.parseInt(s);
            }
          }
          else {
            throw new Exception("Invalid timestamp, " + s.subSequence(0, 4)
                + " is not a valid year");
          }
        }
        catch (NumberFormatException e) {
          if (isSource(s)) {
            setSource(s);
            this.sourceName = s;
          }
          else {
            throw new Exception("Invalid source: " + s + "\n" + e);
          }
        }
        catch (StringIndexOutOfBoundsException e) {
          throw new Exception("Invalid argument: " + s + "\n" + e);
        }

        currentArgument++;
      }
    }
  }

  /**
   * Sets start/end timestamp based on times. If there is no timestamp, it becomes the beginning
   * timestamp If there is, it checks to see if it's before or after the beginning timestamp and
   * assigns them accordingly.
   * 
   * @param timestamp the string of the timestamp in the format of "YYYY-MM-DD".
   * @throws Exception upon error with isSource.
   */
  private void setTimestamp(String timestamp) throws Exception {
    String error = "Invalid timestamp";

    // check if timestamp is a valid YYYY-MM-DD for (year was checked in process() ).
    if (timestamp.charAt(4) != '-') {
      throw new Exception(error + ": format must be YYYY-MM-DD");
    }
    int month = Integer.parseInt(timestamp.substring(5, 7));
    if (month < 1 || month > 12) {
      throw new Exception(error + ": Invalid month " + month + ".  Months are numbered 1 - 12");
    }
    if (timestamp.charAt(7) != '-') {
      throw new Exception(error + ": format must be YYYY-MM-DD");
    }
    int day = Integer.parseInt(timestamp.substring(8, timestamp.length()));
    if (day < 1 || day > 31) {
      throw new Exception(error + ": Invalid day " + day + " days are 1 - 31");
    }

    // make a timestamp with valid string.
    String validTimestamp = timestamp += "T00:00:00.000";
    XMLGregorianCalendar tstamp = Tstamp.makeTimestamp(validTimestamp);

    // assign the timestamp to its appropriate place.
    if (this.timestampStart == null) {
      this.timestampStart = tstamp;
    }
    else {
      if (tstamp.toGregorianCalendar().after(this.timestampStart.toGregorianCalendar())) {
        this.timestampEnd = tstamp;
      }
      else {
        XMLGregorianCalendar temp = this.timestampStart;
        this.timestampStart = tstamp;
        this.timestampEnd = temp;
      }
    }
  }

  /**
   * For use in the case that arguments are invalid.
   */
  public void clear() {
    this.timestampStart = null;
    this.timestampEnd = null;
    this.source = null;
  }

  /**
   * Sets the local source.
   * 
   * @param s the string name of the source.
   * @throws Exception upon error with the sever.
   */
  private void setSource(String s) throws Exception {
    this.source = this.client.getSource(s);
  }

  /**
   * Checks to see if string is a source by calling getSource() with s.
   * 
   * @param s the string to check.
   * @return true if source, false if not or error occurred.
   */
  private boolean isSource(String s) {

    try {
      this.client.getSource(s);
    }
    catch (NotAuthorizedException e) {
      return false;
    }
    catch (ResourceNotFoundException e) {
      return false;
    }
    catch (BadXmlException e) {
      return false;
    }
    catch (MiscClientException e) {
      return false;
    }
    return true;
  }

  /**
   * Getter for the source stored.
   * 
   * @return the source to operate on.
   */
  public Source getSource() {
    return this.source;
  }

  /**
   * Getter for the source name.
   * 
   * @return the source name.
   */
  public String getSourceName() {
    return this.sourceName;
  }

  /**
   * Returns the current timestamp.
   * 
   * @return the beginning timestamp.
   */
  public XMLGregorianCalendar getTimestamp() {
    return this.timestampStart;
  }

  /**
   * Returns the starting timestamp.
   * 
   * @return the beginning timestamp.
   */
  public XMLGregorianCalendar getBeginningTimestamp() {
    return this.timestampStart;
  }

  /**
   * Returns the ending timestamp.
   * 
   * @return the ending timestamp.
   */
  public XMLGregorianCalendar getEndTimestamp() {
    return this.timestampEnd;
  }

  /**
   * Returns the interval.
   * 
   * @return The interval between outputs for the monitoring commands.
   * 
   */

  public int getInterval() {
    return this.interval;
  }

  /**
   * Returns the goal.
   * 
   * @return The goal for the monitor-goal command.
   * 
   */

  public int getGoal() {
    return this.goal;
  }
}